#include "gpio/gpio_driver.h"
#include "gpio/gpio_hal.h"


#if (DEVICE_DEBUG > DEVICE_DEBUG_OFF)
static const char *TAG = "GPIO_DRIVER";
#endif


DEVICE_PUBLIC(static const gpio_hal_driver_ops_t *) gpio_hal_drv_ops = &gpio_stm32f4xx_hal_driver_ops;
DEVICE_PUBLIC(static gpio_private_context_t *) gpio_context_head = NULL;

DEVICE_PUBLIC(static int) gpio_context_add(gpio_private_context_t *context) {
    if (!context) {
        return -1;
    }
    gpio_private_context_t *cur = gpio_context_head;
    while (cur) {
        if (cur == context) {
            return 0;
        }
        cur = cur->next;
    }
    context->next = gpio_context_head;
    gpio_context_head = context;

    return 0;
}

DEVICE_PUBLIC(static int) gpio_context_remove(gpio_private_context_t *context) {
    if (!context || !gpio_context_head) {
        return -1;
    }

    gpio_private_context_t *prev = NULL;
    gpio_private_context_t *cur = gpio_context_head;

    while (cur) {
        if (cur == context) {
            if (prev) {
                prev->next = cur->next;
            } else {
                gpio_context_head = cur->next;
            }
            return 0;
        }
        prev = cur;
        cur = cur->next;
    }

    return -2; // context not found
}


DEVICE_PUBLIC(static int) gpio_driver_device_valid_checked(gpio_device_t *dev) {
    if (!dev || !dev->private_data || !gpio_hal_drv_ops) {
        return -1;
    }

    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->gpio_pin <= GPIO_DEVICE_NUM_MIN || private_context->gpio_pin > GPIO_DEVICE_NUM_MAX) {
        return -2;
    }
    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_open(gpio_device_t *dev, va_list args) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }

    gpio_device_config_t *gpio_cfg = va_arg(args, gpio_device_config_t *);
    if (!gpio_cfg)  return -2;

    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status) {
        return -3;
    }
    memcpy(&private_context->gpio_cfg, gpio_cfg, sizeof(gpio_device_config_t));
    if (gpio_hal_drv_ops->hal_gpio_init(private_context->gpio_pin, &private_context->gpio_cfg) != 0) {
        return -4;
    }
    private_context->open_status = true;

    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_close(gpio_device_t *dev) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }

    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }

    memset(&private_context->gpio_cfg, 0, sizeof(gpio_device_config_t));
    private_context->open_status = false;
    if (gpio_hal_drv_ops->hal_gpio_deinit(private_context->gpio_pin) != 0) {
        return -3;
    }
    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_set_level(gpio_device_t *dev, gpio_device_pin_level_t level) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }
    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }
    if (gpio_hal_drv_ops->hal_gpio_set_level(private_context->gpio_pin, level) != 0) {
        return -3;
    }

    return 0;
}



DEVICE_PUBLIC(static int) gpio_driver_get_level(gpio_device_t *dev) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }
    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }
    return gpio_hal_drv_ops->hal_gpio_get_level(private_context->gpio_pin);
}


DEVICE_PUBLIC(static int) gpio_driver_intr_enable(gpio_device_t *dev) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }
    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }
    private_context->irq_enable_status = true;
    if (gpio_hal_drv_ops->hal_gpio_intr_enable(private_context->gpio_pin) != 0) {
        return -3;
    }
    
    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_intr_disable(gpio_device_t *dev) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }
    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }
    private_context->irq_enable_status = false;
    if (gpio_hal_drv_ops->hal_gpio_intr_disable(private_context->gpio_pin) != 0) {
        return -3;
    }
    
    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_set_intr_type(gpio_device_t *dev, gpio_device_intr_type_t type) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }
    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }
    private_context->gpio_cfg.intr_type = type;
    if (gpio_hal_drv_ops->hal_gpio_set_intr_type(private_context->gpio_pin, private_context->gpio_cfg.intr_type) != 0) {
        return -3;
    }
    
    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_isr_cb_add(gpio_device_t *dev, gpio_isr_cb isr_cb, void *args) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }
    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }
    private_context->isr_cb = isr_cb;
    private_context->args = args;
    
    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_isr_cb_remove(gpio_device_t *dev) {
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -1;
    }
    gpio_private_context_t *private_context = dev->private_data;
    if (private_context->open_status != true) {
        return -2;
    }
    private_context->isr_cb = NULL;
    private_context->args = NULL;
    
    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_ioctl(gpio_device_t *dev, va_list args) {
    if (!dev || !dev->drv || !((driver_t *)dev->drv)->private_ops) {
        return -1;
    }

    gpio_driver_ops_t *gpio_ops = (gpio_driver_ops_t *)((driver_t *)dev->drv)->private_ops;
    gpio_ctrl_cmd_t gpio_ctrl_cmd = (gpio_ctrl_cmd_t)va_arg(args, int);

    switch (gpio_ctrl_cmd) {
        case GPIO_CTRL_CMD_SET_LEVEL: {
            gpio_device_pin_level_t level = (gpio_device_pin_level_t)va_arg(args, int);
            if (!gpio_ops->gpio_set_level) return -2;
            return gpio_ops->gpio_set_level(dev, level);
        }

        case GPIO_CTRL_CMD_GET_LEVEL: {
            if (!gpio_ops->gpio_get_level) return -2;
            return gpio_ops->gpio_get_level(dev);
        }

        case GPIO_CTRL_CMD_INTR_ENABLE: {
            if (!gpio_ops->gpio_intr_enable) return -2;
            return gpio_ops->gpio_intr_enable(dev);
        }

        case GPIO_CTRL_CMD_INTR_DISABLE: {
            if (!gpio_ops->gpio_intr_disable) return -2;
            return gpio_ops->gpio_intr_disable(dev);
        }

        case GPIO_CTRL_CMD_SET_INTR_MODE: {
            gpio_device_intr_type_t intr_type = (gpio_device_intr_type_t)va_arg(args, int);
            if (!gpio_ops->gpio_set_intr_type) return -2;
            return gpio_ops->gpio_set_intr_type(dev, intr_type);
        }

        case GPIO_CTRL_CMD_ISRCB_ADD: {
            gpio_isr_cb cb = va_arg(args, gpio_isr_cb);
            void *cb_args = va_arg(args, void *);
            if (!gpio_ops->gpio_isr_cb_add || !cb) return -2;
            return gpio_ops->gpio_isr_cb_add(dev, cb, cb_args);
        }

        case GPIO_CTRL_CMD_ISRCB_REMOVE: {
            if (!gpio_ops->gpio_isr_cb_remove) return -2;
            return gpio_ops->gpio_isr_cb_remove(dev);
        }

        case GPIO_CTRL_CMD_SET_LOWPOWER_ENTER: {
            if (!gpio_ops->gpio_set_lowpower_enter) return -2;
            return gpio_ops->gpio_set_lowpower_enter(dev);
        }

        case GPIO_CTRL_CMD_SET_LOWPOWER_EXIT: {
            if (!gpio_ops->gpio_set_lowpower_exit) return -2;
            return gpio_ops->gpio_set_lowpower_exit(dev);
        }

        
        default:
            return -3;
    }
}



DEVICE_PUBLIC(static int) gpio_driver_probe(void *dev, void *drv) {
    if (!dev || !drv) {
        return -1;
    }

    gpio_device_t *thisdev = dev;
    driver_t *thisdrv = drv;

    gpio_device_pin_t gpio_pin = (gpio_device_pin_t)(uintptr_t)thisdev->user_data;
    if (gpio_pin <= GPIO_DEVICE_NUM_MIN || gpio_pin > GPIO_DEVICE_NUM_MAX) {
        return -2;
    }
    gpio_private_context_t *private_context = (gpio_private_context_t *)DEVICE_MALLOC(sizeof(gpio_private_context_t));
    if (!private_context) {
        return -3;
    }

    memset(private_context, 0, sizeof(gpio_private_context_t));
    private_context->dev = dev;
    private_context->gpio_pin = gpio_pin;
    thisdev->drv = thisdrv;
    thisdev->private_data = private_context;
    thisdev->ops = thisdrv->ops;
    gpio_context_add(private_context);
    DEVICE_LOG_DEBUG(TAG, "dev[%s] probe drv[%s] success", thisdev->name, thisdrv->name);

    return 0;
}

DEVICE_PUBLIC(static int) gpio_driver_remove(void *dev, void *drv) {
    if (!dev || !drv) {
        return -1;
    }
    if (gpio_driver_device_valid_checked(dev) != 0) {
        return -2;
    }
    gpio_device_t *thisdev = dev;
    gpio_private_context_t *private_context = thisdev->private_data;
    if (private_context) {
        gpio_context_remove(private_context);
        DEVICE_FREE(private_context);
        private_context = NULL;
    }
    thisdev->drv = NULL;
    thisdev->ops = NULL;

    return 0;
}

DEVICE_PUBLIC(void) gpio_driver_isr_handler(gpio_device_pin_t gpio_pin) {
    if (gpio_pin <= GPIO_DEVICE_NUM_MIN || gpio_pin > GPIO_DEVICE_NUM_MAX) {
        return;
    }

    gpio_private_context_t *cur_node = gpio_context_head;
    for (; cur_node; cur_node = cur_node->next) {
        if (cur_node->open_status && cur_node->irq_enable_status && cur_node->isr_cb) {
            if (cur_node->gpio_pin == gpio_pin) {
                cur_node->isr_cb(cur_node->dev,cur_node->args);
            }
        }
    }
}


DEVICE_PUBLIC(static const gpio_driver_ops_t) gpio_driver_ops = {
    .gpio_get_level = gpio_driver_get_level,
    .gpio_set_level = gpio_driver_set_level,
    .gpio_intr_enable = gpio_driver_intr_enable,
    .gpio_intr_disable = gpio_driver_intr_disable,
    .gpio_set_intr_type = gpio_driver_set_intr_type,
    .gpio_isr_cb_add = gpio_driver_isr_cb_add,
    .gpio_isr_cb_remove = gpio_driver_isr_cb_remove,
};

DEVICE_PUBLIC(static const device_ops_t) gpio_device_ops = {
    .open = gpio_driver_open,
    .close = gpio_driver_close,
    .ioctl = gpio_driver_ioctl,
};

DEVICE_PUBLIC(driver_t) gpio_device_driver = {
    .name = "gpio",
    .type = DEVICE_TYPE_GPIO,
    .compat = "generic,gpio",
    .probe = gpio_driver_probe,
    .remove = gpio_driver_remove,
    .ops = &gpio_device_ops,
    .private_ops = &gpio_driver_ops,
    .bus = NULL,
};
